using System.Collections;
using System.Collections.Generic;

namespace Fusion.Utilities.Generics
{
    public class OrderDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
    {
        private class Node
        {
            KeyValuePair<TKey, TValue> keyValue;

            public Node(KeyValuePair<TKey, TValue> keyValue)
            {
                this.keyValue = keyValue;
            }

            public KeyValuePair<TKey, TValue> KeyValue { get => keyValue; set => keyValue = value; }
        }

        Dictionary<TKey, Node> dictionary = new Dictionary<TKey, Node>();
        List<Node> list = new List<Node>();

        public TValue this[TKey key]
        {
            get
            {
                if (dictionary.TryGetValue(key, out Node node))
                {
                    return node.KeyValue.Value;
                }
                else
                {
                    return default;
                }
            }
            set
            {
                var pair = new KeyValuePair<TKey, TValue>(key, value);
                if (dictionary.TryGetValue(key, out Node node))
                {
                    node.KeyValue = pair;
                }
                else
                {
                    Node newNode = new Node(pair);
                    list.Add(newNode);
                    dictionary[key] = newNode;
                }
            }
        }

        public bool ContainsKey(TKey key)
        {
            return dictionary.ContainsKey(key);
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            if (dictionary.TryGetValue(key, out Node node))
            {
                value = node.KeyValue.Value;
                return true;
            }
            else
            {
                value = default;
                return false;
            }
        }

        public TValue Get(int index)
        {
            if (index >= 0 && index < list.Count)
            {
                return list[index].KeyValue.Value;
            }
            else
            {
                return default;
            }
        }

        public TKey GetKey(int index)
        {
            if (index >= 0 && index < list.Count)
            {
                return list[index].KeyValue.Key;
            }
            else
            {
                return default;
            }
        }

        public void Add(TKey key, TValue value)
        {
            this[key] = value;
        }

        public bool Remove(TKey key, out TValue removed)
        {
            if (dictionary.TryGetValue(key, out Node node))
            {
                dictionary.Remove(key);
                list.Remove(node);
                removed = node.KeyValue.Value;
                return true;
            }
            else
            {
                removed = default;
                return false;
            }
        }

        public bool Remove(TKey key)
        {
            if (dictionary.TryGetValue(key, out Node node))
            {
                dictionary.Remove(key);
                list.Remove(node);
                return true;
            }
            else
            {
                return false;
            }
        }

        public void RemoveAt(int index)
        {
            if (index >= 0 && index < list.Count)
            {
                Node node = list[index];
                list.Remove(node);
                dictionary.Remove(node.KeyValue.Key);
            }
        }

        public int Count
        {
            get
            {
                return list.Count;
            }
        }

        public void Clear()
        {
            dictionary.Clear();
            list.Clear();
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            int count = list.Count;
            for (int index = 0; index < count; index++)
            {
                yield return list[index].KeyValue;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
